#include <iostream>
#include <string>

#include <mln/accu/shape/bbox.hh>
#include <mln/accu/math/count.hh>

#include <mln/binarization/threshold.hh>

#include <mln/labeling/blobs.hh>
#include <mln/labeling/compute.hh>
#include <mln/labeling/foreground.hh>

#include <mln/data/transform.hh>
#include <mln/data/paste.hh>

#include <mln/value/rgb8.hh>

#include <mln/core/alias/neighb2d.hh>
#include <mln/core/image/image2d.hh>

#include <mln/logical/not.hh>

#include <mln/draw/box.hh>

#include <mln/literal/colors.hh>

#include <mln/morpho/closing/structural.hh>
#include <mln/morpho/opening/structural.hh>
#include <mln/morpho/erosion.hh>

#include <mln/core/concept/window.hh>
#include <mln/core/concept/function.hh>

#include <mln/io/pbm/save.hh>
#include <mln/io/ppm/load.hh>
#include <mln/io/ppm/save.hh>

using namespace mln;

#include "clear_pink.cc"
#include "output.cc"

#define CELL_ROW		11
#define CELL_COL		30

#define MAX_COL_DELTA_ERR	8
#define MAX_ROW_DELTA_ERR	6

#define MIN_FILLING_RATIO	0.55
#define MAX_FILLING_RATIO	1

#define DENOISE_MIN_CARD	5

template <typename V>
struct rgb_to_luma : public Function_v2v<rgb_to_luma<V> >
{
  rgb_to_luma()
  {
  }

  typedef V result;

  V operator()(const value::rgb8& v) const
  {
    V r = 0;

    r += v.red();
    r += v.green();
    r += v.blue();

    r /= 3;

    return r;
  }
};

void
denoise(image2d<bool> ima,
	unsigned min_card)
{

  unsigned nlabels;
  image2d<unsigned> label_image = labeling::foreground(ima, c4(), nlabels);

  util::array<unsigned>
    counts = labeling::compute(accu::meta::math::count(),
			       label_image,
			       nlabels);

  mln_piter_(image2d<bool>) p(ima.domain());

  for_all(p)
    if (counts(label_image(p)) < min_card)
      ima(p) = false;
}

int
main(int argc, char* argv[])
{
  if ((argc > 4) || (argc < 3))
    {
      std::cerr << "Invalid number of arguments" << std::endl;
      return 0;
    }

  bool generate_qcm = (argc == 4);

  image2d<bool> binary;
  image2d<bool> intermediate;
  image2d<value::rgb8> input;

  {
    io::ppm::load(input, argv[1]);

    clear_pink(input);

    image2d<value::int_u8> gray_image =
      mln::data::transform(input,
			   rgb_to_luma<value::int_u8>());

    binary = binarization::threshold(gray_image, 64);

    logical::not_inplace(binary);

    denoise(binary, DENOISE_MIN_CARD);

    intermediate = image2d<bool>(binary.domain());
    data::paste(binary, intermediate);

    //Following opening is used to invalidate letters
    bool w_vlines[] = {0, 0, 0, 0, 0,
		       0, 0, 0, 0, 0,
		       1, 1, 1, 1, 1,
		       0, 0, 0, 0, 0,
		       0, 0, 0, 0, 0};

    intermediate = morpho::opening::structural(intermediate,
    					 convert::to<window2d>(w_vlines));

    // bool w_merger[] = {1, 1, 1,
    // 		       1, 1, 1,
    // 		       1, 1, 1};

    bool w_merger[] = {1, 1, 1, 1, 1,
    		       1, 1, 1, 1, 1,
    		       1, 1, 1, 1, 1,
    		       1, 1, 1, 1, 1,
    		       1, 1, 1, 1, 1};

    intermediate = morpho::closing::structural(intermediate,
					 convert::to<window2d>(w_merger));

#if 0
    io::pbm::save(intermediate, argv[2]);
#endif
  }

  util::array<box2d> boxes;
  util::array<unsigned> counts;

  {
    unsigned nlabels;
    image2d<unsigned> label_image;
    label_image = labeling::blobs(intermediate, c8(), nlabels);

    counts = labeling::compute(accu::meta::math::count(),
			       label_image,
			       nlabels);

    boxes =
      labeling::compute(accu::meta::shape::bbox(),
			label_image,
			nlabels);
  }

  image2d<bool> output(intermediate.domain());
  data::fill(output, false);

  image2d<value::rgb8> tmpl;

  if (generate_qcm)
    io::ppm::load(tmpl, "./ref/ref.ppm");

  qcm qcm(tmpl);

  {
    int col_delta;
    int row_delta;

    for (unsigned i = 0; i < boxes.nelements(); ++i)
      {
	col_delta = boxes(i).pmax().col() - boxes(i).pmin().col();
	row_delta = boxes(i).pmax().row() - boxes(i).pmin().row();

	if ((col_delta < CELL_COL + MAX_COL_DELTA_ERR)
	    && (col_delta > CELL_COL - MAX_COL_DELTA_ERR)
	    && (row_delta < CELL_ROW + MAX_ROW_DELTA_ERR)
	    && (row_delta > CELL_ROW - MAX_ROW_DELTA_ERR)
	    && (counts[i] > MIN_FILLING_RATIO * boxes(i).nsites())
	    && (counts[i] < MAX_FILLING_RATIO * boxes(i).nsites()))
	  {
	    data::paste(binary | boxes(i), output);
	    draw::box(input, boxes(i), literal::green);

	    if (generate_qcm)
	      qcm.take(boxes(i));
	  }
	else
	  draw::box(input, boxes(i), literal::red);
      }
  }

  io::pbm::save(output, argv[2]);

#if 0
  io::ppm::save(input, std::string(argv[2]) + ".ppm");
#endif

  if (generate_qcm)
    {
      std::ofstream outfile(argv[3]);
      outfile << qcm;
    }

  return 0;
}
